﻿using System.Net;
using System.Text.Encodings.Web;
using System.Text.Json;
using TK.Common.AspNetCore;

namespace TK.Common.Logger
{
    /// <summary>
    /// Extension method used to add the middleware to the HTTP request pipeline.
    /// </summary>
    public static class LoggerMiddlewareExtensions
    {
        /// <summary>
        /// 全局异常处理
        /// </summary>
        public static IApplicationBuilder UseLoggerMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<LoggerMiddleware>();
        }
    }

    /// <summary>
    /// 全局异常处理
    /// </summary>
    public class LoggerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IHostEnvironment _environment;
        private readonly ILogger<LoggerMiddleware> _logger;
        private readonly JsonSerializerOptions _jsonSerializerOptions;
        string traceKey = RequestLoggingOptions.TraceKey;

        /// <summary>
        /// LoggerMiddleWare
        /// </summary>
        public LoggerMiddleware(
            RequestDelegate next,
            IHostEnvironment environment,
            ILogger<LoggerMiddleware> logger)
        {
            _next = next;
            _environment = environment;
            _logger = logger;

            //序列化时，JSON 属性名称使用 camel 大小写
            _jsonSerializerOptions = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                WriteIndented = false,
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
            };
        }

        /// <summary>
        /// Invoke
        /// </summary>
        public async Task Invoke(HttpContext context)
        {
            RequestLoggingMiddleware.SetTraceId(context);

            var logData = new Dictionary<string, object>
            {
                { "traceid", context.TraceIdentifier }
            };

            //保存原始流
            var originalStream = context.Response.Body;
            try
            {
                logData.TryAdd("url", context.Request.Path);
                //请求数据
                var reqData = await ToRequestContent(context);
                logData.TryAdd("request_data", reqData);

                //应答内存流，替换原有流
                var repMS = new MemoryStream();
                //替换原有应答流
                context.Response.Body = repMS;

                //var stopwatch = new Stopwatch();
                //stopwatch.Start();
                await _next(context);
                //stopwatch.Stop();

                //应答数据
                var repData = await ToResponseContent(context, repMS, originalStream);
                logData.TryAdd("response_data", repData);
                logData.TryAdd("status_code", context.Response.StatusCode);
                _logger.LogInformation(JsonSerializer.Serialize(logData, _jsonSerializerOptions));

                //token验证失效，返回相关错误信息
                if (context.Response.StatusCode == 401)
                {
                    await WriteErrorResponseAsync(context, 401, "token验证失败", originalStream);
                }
            }
            catch (BusinessException ex)
            {
                await WriteErrorResponseAsync(context, (int)HttpStatusCode.ExpectationFailed, ex.Message, originalStream);
            }
            catch (Exception ex)
            {
                //记录异常信息
                logData.TryAdd("unhandled", 1);
                logData.TryAdd("ex_source", ex.Source);
                logData.TryAdd("ex_msg", ex.Message);
                logData.TryAdd("ex_trace", ex.StackTrace);
                _logger.LogError(JsonSerializer.Serialize(logData, _jsonSerializerOptions));
                var message = $"服务器内部错误,traceid : {context.TraceIdentifier}";
                await WriteErrorResponseAsync(context, (int)HttpStatusCode.InternalServerError, message, originalStream);
            }
        }

        #region private methods
        private async Task WriteErrorResponseAsync(HttpContext context, int statusCode, string message, Stream origin)
        {
            //发生错误时，格式化返回信息
            var response = new UnaryResult
            {
                StatusCode = statusCode,
                Message = message
            };
            context.Response.Body = origin;
            context.Response.ContentType = "text/json;charset=utf-8;";
            await context.Response.WriteAsync(JsonSerializer.Serialize(response, _jsonSerializerOptions));
        }

        /// <summary>
        /// 获取请求体内容
        /// </summary>
        private async Task<Dictionary<string, string>> ToRequestContent(HttpContext context)
        {
            var result = new Dictionary<string, string>();
            var request = context.Request;
            if (request != null)
            {
                try
                {
                    if (request.Headers != null)
                    {
                        foreach (var item in request.Headers)
                        {
                            result.TryAdd(item.Key, item.Value);
                        }
                    }

                    if (request.HasJsonContentType())
                    {
                        request.EnableBuffering();
                        request.Body.Position = 0;
                        var reader = new StreamReader(request.Body, leaveOpen: true);
                        var data = await reader.ReadToEndAsync();
                        request.Body.Position = 0;
                        result.TryAdd("data", data);
                    }

                    if (request.HasFormContentType)
                    {
                        foreach (var item in request.Form)
                        {
                            result.TryAdd($"form-{item.Key}", item.Value);
                        }

                        if (request.Form.Files != null)
                        {
                            for (var i = 0; i < request.Form.Files.Count; i++)
                            {
                                var formFile = request.Form.Files[i];
                                result.TryAdd($"{formFile.FileName}-{i + 1}", $"{formFile.ContentDisposition}_{formFile.Length}");
                            }
                        }
                    }
                }
                catch { }
            }

            return result;
        }

        /// <summary>
        /// 获取回应内容
        /// </summary>
        private async Task<string> ToResponseContent(HttpContext context, MemoryStream ms, Stream original)
        {
            var response = context.Response;
            var result = string.Empty;
            if (response != null)
            {
                var contentType = response.ContentType ?? string.Empty;
                if (contentType.Contains("application/json") || contentType.Contains("application/xml") || contentType.Contains("text/"))
                {
                    var reader = new StreamReader(ms);
                    ms.Position = 0;
                    result = await reader.ReadToEndAsync();
                }

                //恢复原始流
                ms.Position = 0;
                await ms.CopyToAsync(original);
                context.Response.Body = original;
            }

            await ms.DisposeAsync();
            return result;
        }
        #endregion
    }
}